package stmlib;
import java.util.*;

/** A Transactional java.util.Map.  Protected by a single read-write lock. */
public class StmMap<K,V> extends Lockable implements Map<K,V> {
    Map<K,V> map = new HashMap<K,V>();
    public StmMap(Map<K,V> source, LockManager lm) {this.map = source; this.lm = lm; }
    public StmMap() { this.map = new HashMap<K,V>(); this.lm = new LockManager(); }

    public void clear() {
        lm.writeLock();
        final Map<K, V> original = map;
        map = new HashMap<K,V>();

        lm.logUndo(new Runnable(){
            public void run() { map = original; } //replace map with original map
        });
    }

    public boolean containsKey(Object key) { lm.readLock(); return map.containsKey((K)key); }

    public boolean containsValue(Object value) { lm.readLock(); return map.containsValue((V)value); }

    public Set<Map.Entry<K,V>> entrySet() {
        return new StmSet<Entry<K, V>>(map.entrySet(), lm);
    }

    public V get(Object key) { lm.readLock(); return map.get((K)key); }

    public boolean isEmpty() { lm.readLock(); return map.isEmpty(); }

    public Set<K> keySet() {
        return new StmSet<K>(map.keySet(), lm){
            public boolean remove(Object o){ return StmMap.this.remove((K)o) != null; }
        };
    }

    public V put(final K key, V value) {
        lm.writeLock();
        final V oldValue = map.put(key, value);
        lm.logUndo(new Runnable(){
            public void run() {
                if (oldValue != null) map.put(key, oldValue);
                else map.remove(key);
            }
        });
        return oldValue;
    }

    public void putAll(Map<? extends K,? extends V> map2) {
        //simplest implementation. not optimal, but this operation isn't that optimizable:
        for (Map.Entry<? extends K, ? extends V> entry2 : map2.entrySet()){
            put(entry2.getKey(), entry2.getValue()); //handles locking and undo logging.
        }
    }

    public V remove(final Object key){
        lm.writeLock();
        final V previousValue = map.remove((K)key);
        if (previousValue != null) lm.logUndo(new Runnable(){
            public void run() { map.put((K)key, previousValue); }
        }); //if previous value is null, no undo is required!
        return previousValue;
    }

    public int size() { lm.readLock(); return map.size(); }

    public Collection<V> values() {
        return new AbstractCollection<V>() {
            public int size() { return StmMap.this.size(); }
            public boolean isEmpty() { return StmMap.this.isEmpty(); }
            public void clear() { StmMap.this.clear(); }
            public Iterator<V> iterator() {
                return new StmIterator<V>(map.values().iterator(), lm);
            }
        };
    }

    public StmMap<K,V> lock() { lm.writeLock(); return this; }
    public String toString() { return toString(map); }

    /** Create a StmMap with LRU semantics. */
    public static<K,V> StmMap<K, V> makeLRU(final int size) {
        final Map<K, V> map = new LinkedHashMap(size, 0.75F, true) {
            protected boolean removeEldestEntry(Map.Entry eldest) {
                return size() > size; // we already hold a write lock (insertion).
            }
        };
        return new StmMap<K,V>(map, new LockManager());
    }
}